package com.wxzhou.wangzhuan.service;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wxzhou.wangzhuan.dto.AdReportInfo;
import com.wxzhou.wangzhuan.dto.UserInfoDto;
import com.wxzhou.wangzhuan.dto.config.SystemConfigDto;
import io.quarkus.redis.datasource.ReactiveRedisDataSource;
import io.quarkus.redis.datasource.hash.ReactiveHashCommands;
import io.quarkus.redis.datasource.keys.ReactiveKeyCommands;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 阅读频率限制
 */
@ApplicationScoped
public class ReaderRateLimitService {

    private static final Logger LOG = Logger.getLogger(ReaderRateLimitService.class);

    private static final String AD_LIMIT_PREFIX = "reader:limit:";

    private final ReactiveHashCommands<String, String, String> hashCommands;

    private final ReactiveKeyCommands<String> keyCommands;

    private final ObjectMapper objectMapper;

    @Inject
    ConfigService configService;

    @Inject
    public ReaderRateLimitService(ReactiveRedisDataSource redisDataSource, ObjectMapper objectMapper) {
        this.hashCommands = redisDataSource.hash(String.class, String.class, String.class);
        keyCommands = redisDataSource.key(String.class);
        this.objectMapper = objectMapper;
        this.objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 检查本次广告是否允许拉取，从redis中检查
     * 本次请求bs_ac=0，不写入
     *
     * @param sense       场景
     * @param app_channel app渠道
     * @param deviceId    设备
     * @param bs_ac       时机
     * @param userInfo
     * @return
     */
    public Uni<Boolean> isAllowed(String adidsn, int sense, String app_channel, String deviceId, int bs_ac, UserInfoDto userInfo) {

        // 构建Redis键
        String redisKey = buildAdLimitKey(userInfo.getUserId());

        // 获取系统配置
        Uni<SystemConfigDto> systemConfig = configService.getSystemConfig();

        // 如果bs_ac为0，表示查询并存储
        if (bs_ac == 0) {
            // 查询当前限制是否已达到
            return checkLimitReached(redisKey, deviceId, app_channel, sense, systemConfig)
                    .chain(allowed -> {
                        if (allowed) {
                            // 如果允许拉取，则存储记录
                            return recordAdRequest(adidsn, sense, app_channel, deviceId, bs_ac, userInfo)
                                    .onItem().transform(ignore -> true);
                        } else {
                            return Uni.createFrom().item(false);
                        }
                    });
        } else {
            throw new RuntimeException("时机正确");
        }
    }

    /**
     * 存储广告预拉取记录,并且存储的value是按AdBusinessAction的code作为键存储,比如是:
     * 预拉时存储
     * 广告上报后,存入,防止多次拉取!
     *
     * @param sense       场景
     * @param app_channel app渠道
     * @param deviceId    设备ID
     * @param bs_ac       发生时机
     * @param userInfo    用户信息
     * @return
     */
    public Uni<Void> recordAdRequest(String adidsn, int sense, String app_channel, String deviceId, int bs_ac, UserInfoDto userInfo) {
        // 获取当前日期，格式为yyyyMMdd

        // 构建Redis键
        String redisKey = buildAdLimitKey(userInfo.getUserId());

        // 存储当前时间戳并设置24小时过期时间
        String currentTimeMillisStr = String.valueOf(System.currentTimeMillis());

        // 构建Hash字段名，需要增加时间：
        String hashField = deviceId + "_" + app_channel + "_" + sense + "_" + bs_ac + "_" + currentTimeMillisStr;

        return hashCommands.hset(redisKey, hashField, adidsn)
                .chain(() -> keyCommands.expire(redisKey, TimeUnit.HOURS.toSeconds(24)))
                .onItem().ignore().andContinueWithNull();
    }

    public Uni<Void> recordAdRequest(String value, int sense, String app_channel, String deviceId, int bs_ac, String userid) {
        // 获取当前日期，格式为yyyyMMdd

        // 构建Redis键
        String redisKey = buildAdLimitKey(Long.valueOf(userid));

        // 存储当前时间戳并设置24小时过期时间
        String currentTimeMillisStr = String.valueOf(System.currentTimeMillis());

        // 构建Hash字段名，需要增加时间：
        String hashField = deviceId + "_" + app_channel + "_" + sense + "_" + bs_ac + "_" + currentTimeMillisStr;

        return hashCommands.hset(redisKey, hashField, value)
                .chain(() -> keyCommands.expire(redisKey, TimeUnit.HOURS.toSeconds(24)))
                .onItem().ignore().andContinueWithNull();
    }

    /**
     * 检查是否达到频率限制
     *
     * @param redisKey        Redis键
     * @param deviceId        设备ID
     * @param app_channel     应用渠道
     * @param sense           场景
     * @param systemConfigUni 系统配置
     * @return 是否允许拉取广告
     */
    private Uni<Boolean> checkLimitReached(String redisKey, String deviceId, String app_channel, int sense, Uni<SystemConfigDto> systemConfigUni) {
        // 获取所有Hash字段
        return hashCommands.hgetall(redisKey)
                .onItem().transformToUni(map -> {
                    return systemConfigUni.onItem().transform(systemConfig -> {
                        // 检查dayMaxAdCountOfDevice限制（单设备）
                        Integer dayMaxAdCountOfDevice = systemConfig.getDayMaxAdCountOfDevice();
                        if (dayMaxAdCountOfDevice != null && dayMaxAdCountOfDevice > 0) {
                            long deviceCount = map.keySet().stream()
                                    .filter(key -> key.startsWith(deviceId + "_"))
                                    .filter(key -> validByBusinessAction(key))
                                    .count();

                            if (deviceCount >= dayMaxAdCountOfDevice) {
                                LOG.errorf("预拉广告失败:设备%s拉取次数超过当日限制%s次", deviceId, dayMaxAdCountOfDevice);
                                return false; // 超过单设备限制
                            }
                        }

                        // 检查dayMaxAdCount限制（单渠道包+单设备）
                        Integer dayMaxAdCount = systemConfig.getDayMaxAdCount();
                        if (dayMaxAdCount != null && dayMaxAdCount > 0) {
                            long appDeviceCount = map.keySet().stream()
                                    .filter(key -> key.startsWith(deviceId + "_" + app_channel + "_"))
                                    .filter(key -> validByBusinessAction(key))
                                    .count();

                            if (appDeviceCount >= dayMaxAdCount) {
                                LOG.errorf("预拉广告失败:app渠道包%s拉取次数超过当日限制%s次", app_channel, dayMaxAdCount);
                                return false; // 超过单渠道包+单设备限制
                            }
                        }

                        // 检查dayMaxAdCountOfSenses限制（场景）和时间间隔
                        String senseStr = String.valueOf(sense);
                        Map<String, Integer> senseLimits = systemConfig.getDayMaxAdCountOfSenses();
                        Map<String, Integer> senseInterTimes = systemConfig.getDayMaxAdCountOfInterTime();

                        // 检查场景次数限制
                        if (senseLimits != null && senseLimits.containsKey(senseStr)) {
                            int senseLimit = senseLimits.get(senseStr);//获取系统配置的最大次数
                            if (senseLimit > 0) {
                                long senseCount = map.keySet().stream()
                                        .filter(key -> key.startsWith(deviceId + "_" + app_channel + "_" + sense + "_"))
                                        // 只统计bs_ac!=0的次数，表示只根据拉取结果来判定
                                        .filter(key -> validByBusinessAction(key))
                                        .count();

                                if (senseCount >= senseLimit) {
                                    LOG.errorf("预拉广告失败:广告场景%s拉取次数超过当日限制%s次", senseStr, senseLimit);
                                    return false; // 超过场景限制
                                }
                            }
                        }

                        // 检查场景时间间隔限制
                        if (senseInterTimes != null && senseInterTimes.containsKey(senseStr)) {
                            int interTime = senseInterTimes.get(senseStr);
                            if (interTime > 0) {
                                // 查找最近一次该场景的请求时间
                                long latestTime = map.entrySet().stream()
                                        .filter(entry -> entry.getKey().startsWith(deviceId + "_" + app_channel + "_" + sense + "_"))
                                        // 只考虑bs_ac!=0的记录
//                                        .filter(entry -> validByBusinessAction(entry.getKey()))
                                        .mapToLong(entry -> {
                                            try {
                                                return getLastTime(entry.getKey());
                                            } catch (NumberFormatException e) {
                                                return 0L;
                                            }
                                        })
                                        .max()
                                        .orElse(0L);  // 获取最大值，如果不存在则返回0

                                // 计算时间间隔是否满足要求, 如果时间间隔不满足要求，则不允许拉取
                                long timeMinus = System.currentTimeMillis() - latestTime;
                                if (timeMinus <= interTime) {
                                    LOG.errorf("预拉广告失败:2次刚刚拉取时间间隔%s", timeMinus);
                                    return false;
                                }
                            }
                        }

                        return true; // 未超过任何限制
                    });
                });
    }

    //根据抽取有效拉取次数来计算！
    private static boolean validByBusinessAction(String key) {
        String[] parts = key.split("_");
        if (parts.length >= 4) {
            try {
                int bsAcValue = Integer.parseInt(parts[3]);
                return bsAcValue != 0;
            } catch (NumberFormatException e) {
                return false;
            }
        }
        return false;
    }

    /**
     * 最后存时间
     *
     * @param key
     * @return
     */
    private static long getLastTime(String key) {
        String[] parts = key.split("_");
        if (parts.length >= 5) {
            try {
                Long lastime = Long.valueOf(parts[parts.length - 1]);
                return lastime;
            } catch (Exception e) {
                return 0;
            }
        }
        return 0;
    }

    private String buildAdLimitKey(Long userid) {
        // 获取当前日期，格式为yyyyMMdd
        String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));

        // 构建Redis键
        String redisKey = AD_LIMIT_PREFIX + currentDate + ":" + userid;
        return redisKey;
    }

}
